<?PHP
// Copyright (c) 2001-2003 ars Cognita, Inc., all rights reserved
/********************************************************************************
	
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; version 2 of the License.
   
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
   
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   
*******************************************************************************/
/**
 * tackle - Tiny ACL Authentication Library for PHP
 *
 * Tackle class and methods.
 *  
 * @author		$Author: richtl $
 * @version		$Revision: 1.18 $
 *
 * @package tackle
 */



/**
* class tackle 
*
* The base class for the tackle toolset. Provides database access methods and
* methods common to all database objects.
*
* @access	private
*
* @package	tackle
*
* @author 	$Author: richtl $
* @version 	$Revision: 1.18 $
* @since 	$Date: 2003/10/21 21:19:08 $
*
* @copyright 	ars Cognita, Inc. 2002
*/
class tackle {
	
	/**
	* @var object	Database connection object.
	* @access 	private
	*/
	var $dbconn;
	
	/**
	* @var array	List of valid object types.
	* @access 	private
	*/
	var $objectTypes;
	
	/**
	* Class constructor
	*
	* Initialize the database connection.
	*
	* access	public
	*/
	function tackle( $debug = TRUE ) {
		
		$this->objectTypes = array( 'member','group','membership',
			'resource','action','permission' );
		
		
		
		// Create the connection and login to the database.
	    $dbconn = &NFactory::getDBO();
		$dbconn->debug = $debug;
		
		// Make the connection accessable as a class property
		$this->dbconn = $dbconn;
	}
	
	/**
	* Destructor
	*
	* Close out any open database connections
	*
	* @access	public
	*/
	function close() {
		$this->dbconn->Close();
	}
	
	////////////////////////////////////////////////////////////////////////
	// General Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		* Fetch objid given ident
		*
		* Return the objid of an object given it's identity
		*
		* @access	private
		*
		* @param	string		$objType	Type of object
		* @param	string		$ident		Identity of object
		* @return	mixed		objid if successful, else FALSE
		*/
		function getObjid( $objType, $ident ) {
			
			$returnCode = FALSE;
			
			if ( in_array( $objType, $this->objectTypes ) ) {
				$dbconn = $this->dbconn;
				
				$sql = "SELECT objid,1 FROM #__tackle_$objType WHERE ident = '$ident'";
				$record = $dbconn->Execute( $sql );
				if ( $record ) {
					if (isset($record->data[0]))
					$returnCode = $record->data[0][0];
				}
			}
			
			return $returnCode;
		}
		/**
		*
		* Verify objid exists
		*
		* Verify an instance exists with the given objid
		*
		* @access	private
		*
		* @param	string		$objType	Object type
		* @param	int			$objid		Objid to check
		*
		* @return	mixed		TRUE if exists, else FALSE
		*
		*/
		function checkObjid( $objType, $objid ) {
			
			$returnCode = FALSE;
			
			if ( $objid && in_array( $objType, $this->objectTypes ) ) {
				$dbconn = $this->dbconn;
				$sql = "SELECT objid FROM #__tackle_$objType WHERE objid = $objid";
				$record = $dbconn->Execute( $sql );
				if ( $record && $record->recordCount() == 1 ) {
					$returnCode = TRUE;
				}
			}
			return $returnCode;
		}
		
		/**
		*
		* Remove an object by objid
		*
		* Connect to the database and delete the specified instance.
		*
		* @access	private
		*
		* @param	int		$objid		objid of object instance
		*
		* @return	boolean		FALSE if objid still exists after removal.
		*
		*/
		function removeByObjid( $objType, $objid ) {
			
			$returnCode = FALSE;
			
			if ( in_array( $objType, $this->objectTypes ) ) {
				
				if ( $this->checkObjid( $objType, $objid ) ) {
					$dbconn = $this->dbconn;
					$sql = "DELETE FROM #__tackle_$objType WHERE objid = $objid";
					$dbconn->Execute( $sql );
					$returnCode = $this->checkObjid( $objType, $objid );
				}
			}
			return !$returnCode;
		}
		
		/**
		*
		* Change an instance of an object given the objid
		*
		* Connect to the database and delete the specified instance.
		*
		* @access	private
		*
		* @param	string		$objType	Type of object
		* @param	int			$objid		Objid of instance
		* @param	string		$variable	Field/Property to change
		* @param	variable	$value		New value of field/property
		*
		* @return	boolean		TRUE if successful, else FALSE
		*
		*/
		function commitByObjid( $objType, $objid, $variable, $value ) {
			
			$returnCode = FALSE;
			
			if ( $this->checkObjid( $objType, $objid ) ) {
				
				// NOTE: if $value is a string type, run security check on it.
				// Otherwise, verify it is what it's supposed to be.
				
				// IDEA: Maybe verify the variable exists in the metadata.
				
				// Connect to the database and use GetUpdateSQL to update
				// the object with the given ident.
				$db = $this->dbconn;
				
				$sql = "SELECT * FROM #__tackle_".$objType." WHERE objid = $objid";
				 $db->setQuery( $sql );
				$oRecord = $db->loadObject();
				
				if ( $oRecord) {
					$object = new stdClass;
					$object->$variable=$value;
					$object->objid=$objid;
					$returnCode = $db->updateObject( "#__tackle_".$objType, &$object, 'objid' );
				}
			}
			return $returnCode;
		}
		
		/**
		*
		* Add a new instance of an object to the database
		*
		* Connect to the database and add a new instance of the requested 
		* object. This is generally called by a wrapper for the specific object.
		*
		* @access	private
		*
		* @param	string		$objType	Type of object (member, group, etc.)
		* @param	string		$ident		Identity of instance
		* @param	string		$descr		Description of instance
		* @param	boolean		$enabled	Active status of the instance
		*
		* @return	int			objid of new object, else FALSE
		*
		*/
		function create( $objType, $ident, $descr = NULL, $enabled = TRUE ) {
			
			if ( in_array( $objType, $this->objectTypes ) ) {
			
				if ( !isset( $descr ) ) $descr = $ident;
				$enabled ? $enabled = 1 : $enabled = 0;
				
				// Strip double quotes from the ident string
				$ident = strtr( $ident, "\"", " " );
				
				// Connect to the database and use GetInsertSQL to insert the new object. 
				//Do it all in a transaction, so we don't generate extraneous objids on errors.
				
				$dbconn = $this->dbconn;
				
				// Select an empty record from the database. If the record count isn't zero
				// the user already exists.
				$sql = "SELECT * FROM #__tackle_$objType WHERE ident = '$ident'";
				//$db->BeginTrans();
				$record = $dbconn->Execute( $sql );
				if ( $record && $record->RecordCount() == 0 ) {
					// Initialize and build the new record
					$newRecord = new stdClass();
					
					
					//$newRecord->objid = $newObjid;
					//$newRecord->ident = $ident;
					//$newRecord->descr = $descr;
					
					$newRecord->enabled = $enabled;
					
					$dbconn->insertObject( "#__tackle_$objType", $newRecord, 'objid' );
					if ( $ident == NULL || $ident == '' ) $ident = $newRecord->objid;
					if ( $descr == NULL || $descr == '' ) $descr = $newRecord->ojbid;
					$newRecord->descr = $descr;
					$newRecord->ident = $ident;
					$dbconn->updateObject( "#__tackle_$objType", $newRecord, 'objid' );
					
					/*
					if ( $db->Execute( $insertSql ) ) {
						$db->CommitTrans();
						$returnCode = $ident;
					} else {
						$db->RollbackTrans();
					}*/
				}
			}
			
			return $this->getObjid( $objType, $ident );
		}
		
		/**
		*
		* Remove an instance of an object from the database
		*
		* Connect to the database and delete the specified instance.
		*
		* @access	private
		*
		* @param	string		$ident		Identity of instance
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function remove( $objType, $ident ) {
			
			$returnCode = FALSE;
			
			if ( in_array( $objType, $this->objectTypes ) ) {		
				if ( $ident ) {	
					$returnCode = $this->removeByObjid( $objType, $this->getObjid( $objType, $ident ) );
				}
			}
			return $returnCode;
		}
		
		/**
		*
		* Change an instance of an object from the database
		*
		* Connect to the database and delete the specified instance.
		*
		* @access	private
		*
		* @param	string		$objType	Type of object
		* @param	string		$ident		Identity of instance
		* @param	string		$variable	Field/Property to change
		* @param	variable	$value		New value of field/property
		*
		* @return	boolean		TRUE if successful, else FALSE
		*
		*/
		function commit( $objType, $ident, $variable, $value ) {
			
			$returnCode = FALSE;
			
			$returnCode = $this->commitByObjid( $objType, $this->getObjid( $objType, $ident ), $variable, $value );
			
			return $returnCode;
		}
	
	////////////////////////////////////////////////////////////////////////
	// Fundamental Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Change the identity of an instance
		*
		* Change the identity of the current object and commit to
		* the database.
		*
		* @param	string		$objType	Type of object (member, group, etc.)
		* @param	string		$oldIdent	Current identity of object
		* @param	string		$newIdent	New identity
		*
		* access	public
		*
		*/
		function changeIdent( $objType, $oldIdent, $newIdent ) {
			return $this->commit( $objType, $oldIdent, 'ident', $newIdent );
		}
		
		/**
		*
		* Change the description of an instance
		*
		* Change the description of the current object and commit to
		* the database.
		*
		* @param	string		$objType	Type of object (member, group, etc.)
		* @param	string		$ident		Identity of object
		* @param	string		$descr		New description
		*
		* access	public
		*
		*/
		function changeDescr( $objType, $ident, $descr ) {
			return $this->commit( $objType, $ident, 'descr', $descr );
		}
		
		/**
		*
		* Enable an instance
		*
		* Set the status flag for the current object to enabled and commit to
		* the database.
		*
		* @param	string		$objType	Type of object (member, group, etc.)
		* @param	string		$ident		Identity of object
		*
		* access	public
		*
		*/
		function enable( $objType, $ident ) {
			return $this->commit( $objType, $ident, 'enabled', 1 );
		}
		
		/**
		*
		* Disable an instance
		*
		* Set the status flag for the current object to disabled and commit to
		* the database.
		*		
		* @param	string		$objType	Type of object (member, group, etc.)
		* @param	string		$ident		Identity of object
		*
		* access	public
		*
		*/
		function disable( $objType, $ident ) {
			return $this->commit( $objType, $ident, 'enabled', 0 );
		}
		
		/**
		*
		* Link the object to a parent
		*
		* Create a hierarchy by linking the object to a parent object.
		* The object will inherit permissions from the parent object.
		*
		* @param	string		$objType		Type of object (member, group, etc.)
		* @param	string		$ident			Identity of object
		* @param	string		$parentIdent	Identity of parent
		*
		* @access	private
		*
		* @param	int		Objid of parent
		*
		*/
		function linkParent( $objType, $ident, $parentIdent = NULL ) {
			
			// NOTE: Make sure none of the ancestors are descendants.
			
			// If the parent is the self, there's no parent.
			$parentIdent != $ident ? $parentObjid = $this->getObjid( $objType, $parentIdent ) : $parentObjid = 0;
			if ( $parentObjid && $parentObjid > 0 ) $this->commit( $objType, $ident, 'parent', $parentObjid );
			
			return $parentObjid;
		}
		
		/**
		*
		* Unlink instance from parent
		*
		* Disenherit the group from its parent.
		*
		* @param	string		$objType		Type of object (member, group, etc.)
		* @param	string		$ident			Identity of object
		*
		* @access	private
		*
		* @return	string	Identity of group
		*
		*/
		function unlinkParent( $objType, $ident ) {
			$this->commit( $objType, $ident, 'parent', 0 );
		}
		
		/**
		*
		* Fetch parent objid given objid
		*
		* Return the objid of an object's parent given and objid
		*
		* @access	private
		*
		* @param	string		$objType	Type of object
		* @param	int			$objid		Identity of object
		*
		* @return	int			objid of parent if successful, else NULL
		*
		*/
		function getParentByObjid( $objType, $objid ) {
			
			$returnCode = FALSE;
			
			if ( in_array( $objType, $this->objectTypes ) && $objid ) {
				$dbconn = $this->dbconn;
				
				$sql = "SELECT parent FROM #__tackle_$objType WHERE objid = $objid";
				$record = $dbconn->Execute( $sql );
				if ( $record ) {
					if (isset($record->fields))
					$returnCode = $record->fields[0];
				}
			}
			return $returnCode;
		}
		
	////////////////////////////////////////////////////////////////////////
	// Member Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Create new member
		*
		* Wrapper to add a new member to the database
		*
		* @access	public
		*
		* @param	string		$ident		Member identity
		* @param	string		$descr		Member description
		* @param	boolean		$enabled	Member status
		*
		* @return	int			objid of new member, else FALSE
		*
		*/
		function createMember( $ident, $descr = NULL, $enabled = TRUE ) {
			require_once(FRAMEWORK_DIR."/libraries/neocasta/user/helper.php");
			if (!NHelperUser::getUserId($ident)) return false;
			if ( !$descr ) $descr = $ident;
			return $this->create( 'member', $ident, $descr, $enabled );
		}
		
		/**
		*
		* Remove a member
		*
		* Wrapper to remove a member from the database. Also removes any
		* permissions attached to the member.
		*
		* @access	public
		*
		* @param	string		$ident		Member identity
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeMember( $ident ) {
			$returnCode = FALSE;
			$this->removePermission( "member", $ident );
			$returnCode = $this->remove( 'member', $ident );
			return $returnCode;
		}
		
		/**
		*
		* Get list of groups member belongs to
		*
		* Return the list of groups the member belongs to as an array.
		*
		* @access	public
		*
		* @param	string		$ident		Member identity
		*
		* @return	array		List of group memberships (objid, ident)
		*
		*/
		function memberships( $ident ) {
				
			$returnCode = FALSE;
			
			$objid = $this->getObjid( 'member', $ident );
			if ( $objid ) {
				$dbconn = $this->dbconn;
				$sql = "SELECT groupid, g.ident FROM #__tackle_membership, #__tackle_group g "
					."WHERE groupid = g.objid and memberid = $objid";
				$record = $dbconn->setQuery( $sql );
				$groups = $dbconn->loadAssoc();
				$returnCode = $groups;
			}
			return $returnCode;
		}
		
		/**
		*
		* Wrapper to check member for requested permissions
		*
		* Check a member for access to the specified resource and action.
		*
		* @access	public
		*
		* @param	string		$ident		Identity of member
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		*
		* @return	string		TRUE if member is allowed access, else FALSE
		*
		*/
		function authorizedMember( $ident, $action, $resource ){
			
			$returnCode = FALSE;
			
			$auth = $this->authorizedRequestor( 'member', $ident, $action, $resource );
			if ( $auth != 'ALLOW' ) {
				$membership = $this->memberships( $ident );
				if ( $membership ) {
					foreach( $membership as $groupObjid => $groupIdent ) {
						$groupAuth = $this->authorizedGroup( $groupIdent, $action, $resource );
						if ( $groupAuth == 'ALLOW' ) $auth = 'ALLOW';
					}
				}
			}
			$returnCode = $auth;
			return $returnCode;
		}
	
	
	////////////////////////////////////////////////////////////////////////
	// Group Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Create new group
		*
		* Wrapper to add a new group to the database
		*
		* @access	public
		*
		* @param	string		$ident		Group identity
		* @param	string		$descr		Group description
		* @param	boolean		$enabled	Group status
		*
		* @return	int			objid of new group, else FALSE
		*
		*/
		function createGroup( $ident, $descr = NULL, $enabled = TRUE ) {
			if ( !$descr ) $descr = $ident;
			return $this->create( 'group', $ident, $descr, $enabled );
		}
		
		/**
		*
		* Remove a group
		*
		* Wrapper to remove a group from the database. Also remove all
		* memberships and dereference all children.
		*
		* @access	public
		*
		* @param	string		$ident		Group identity
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeGroup( $ident ) {
				
			$returnCode = FALSE;
			
			// Remove memberships and permissions and unlink children before removing group
			$this->removePermission( "group", $ident );
			$returnCode = $this->removeGroupChildren( $ident );
			$returnCode = $returnCode && $this->removeGroupMembers( $ident );
			$returnCode = $returnCode && $this->remove( 'group', $ident );
			
			return $returnCode;
		}
		
		/**
		*
		* Remove all memberships from a group
		*
		* Remove all memberships that reference this group
		*
		* @access	public
		*
		* @param	string		$ident	identity of group
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeGroupMembers( $ident ) {
			
			$returnCode = FALSE;
			
			$objid = $this->getObjid( 'group', $ident );
			if ( $objid ) {
				$dbconn = $this->dbconn;
				$sql = "DELETE FROM #__tackle_membership WHERE groupId = $objid";
				$dbconn->Execute( $sql );
				$returnCode = TRUE;
			}
			return $returnCode;
		}
		
		/**
		*
		* Dereference all children referencing group
		*
		* Dereference all children that reference this group.
		*
		* @access	private
		*
		* @param	string		$ident	identity of object instance
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeGroupChildren( $ident ) {
			
			$returnCode = FALSE;
			
			$objid = $this->getObjid( 'group', $ident );	
			if ( $objid ) {
				$dbconn = $this->dbconn;
				$sql = "UPDATE #__tackle_group SET parent = NULL WHERE parent = $objid";
				$dbconn->Execute( $sql );
				$returnCode = TRUE;
			}
			return $returnCode;
		}
		
		/**
		*
		* Add member to a group
		*
		* Add the member to the specified group.
		*
		* @access	public
		*
		* @param	string	Identity of group
		* @param	string	Identity of member (default to instance property)
		*
		* @return	string	Identity of group
		*
		*/
		function addToGroup( $groupIdent, $memberIdent ) {
			
			$returnCode = FALSE;
			
			$memberObjid = $this->getObjid( 'member', $memberIdent );
			$groupObjid = $this->getObjid( 'group', $groupIdent );
			
			if ( $memberObjid && $groupObjid ) {
				
				$db = $this->dbconn;
				
				// Select an empty record from the database. If the record count isn't zero
				// the user already exists.
				$sql = "SELECT * FROM #__tackle_membership WHERE memberId = $memberObjid AND groupId = $groupObjid";
				$record = $db->Execute( $sql );
				if ( $record && $record->RecordCount() == 0 ) {
					// Initialize and build the new record
					$newRecord = new stdClass();
					$newRecord->memberid = $memberObjid;
					$newRecord->groupid = $groupObjid;
					
					// Insert the record into the database
					$insertSql = $db->insertObject( '#__tackle_membership', $newRecord );
					$returnCode = $groupIdent;
				}
			}
			
			return $returnCode;
		}
		
		/**
		*
		* Remove member from a group
		*
		* Remove a specific member this group
		*
		* @access	public
		*
		* @param	string		$groupIdent		identity of group
		* @param	string		$memberIdent	identity of member
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeFromGroup( $groupIdent, $memberIdent ) {
			
			$returnCode = FALSE;
			
			$groupObjid = $this->getObjid( 'group', $groupIdent );
			$memberObjid = $this->getObjid( 'member', $memberIdent );
			
			if ( $groupObjid && $memberObjid ) {
				$dbconn = $this->dbconn;
				$sql = "DELETE FROM #__tackle_membership WHERE groupId = $groupObjid and memberId = $memberObjid";
				$dbconn->Execute( $sql );
				$returnCode = TRUE;
			}
			return $returnCode;
		}
		
		/**
		*
		* Link group to a parent group
		*
		* Link a group to another group (the parent). The child will
		* eventually inherit characteristics from the parent.
		*		
		* @param	string		$ident				Identity of group
		* @param	string		$parentIdent		Identity of parent group
		*
		* access	public
		*
		*/
		function linkGroupToParent( $ident, $parentIdent ) {
			return $this->linkParent( 'group', $ident, $parentIdent );
		}
		
		/**
		*
		* Unlink group from parent
		*
		* Disenherit the group from its parent.
		*
		* @param	string		$ident				Identity of group
		*
		* @access	public
		*
		* @return	string	Identity of group
		*
		*/
		function unlinkGroupFromParent( $ident ) {
			return $this->unlinkParent( 'group', $ident );
		}
		
		/**
		*
		* Wrapper to check group for requested permissions
		*
		* Check a group for access to the specified resource and action.
		*
		* @access	public
		*
		* @param	string		$ident		Identity of group
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		*
		* @return	string		TRUE if group is allowed access, else FALSE
		*
		*/
		function authorizedGroup( $ident, $action, $resource ){
			
			return $this->authorizedGroupByObjid( 
					$this->getObjid( 'group', $ident ),
					$this->getObjid( 'action', $action ), 
					$this->getObjid( 'resource', $resource ) );
		}
		
		/**
		*
		* Wrapper to check group for requested permissions by objid
		*
		* Check a group for access to the specified resource and action given
		* the objid of the group, action, and resource. This is a recursive
		* method. Be careful when messing around with it!
		*
		* @access	private
		*
		* @param	int		$groupObjid		Objid of group
		* @param	int		$actionObjid	Objid of action to check
		* @param	int		$resourceObjid	Objid of action's resource
		*
		* @return	bool		TRUE if group is allowed access, else FALSE
		*
		*/
		function authorizedGroupByObjid( $groupObjid, $actionObjid, $resourceObjid ) {
			
			$returnCode = FALSE;
			
			// Make sure we have necessary objids
			if ( $groupObjid && $actionObjid && $resourceObjid ) {
				
				$auth = $this->authorizedRequestorByObjid( 'group', $groupObjid, $actionObjid, $resourceObjid );
				
				// If authorized, we can stop here. Else check parent.
				// Here's the fun recursive part.
				
				if ( $auth != 'ALLOW' ) {
					
					// Hang in with me here. If our group has a parent, the groupObjid is replaced by 
					// the current group's parent. Parent is now the new group's (the old parent's) 
					// parent. I.e., the grandparent.
					$groupObjid = $this->getParentByObjid( 'group', $groupObjid );
					if ( $groupObjid ) {
						$auth = $this->authorizedGroupByObjid( $groupObjid, $actionObjid, $resourceObjid );
					}
				}
				$returnCode = $auth;
			}
			return $returnCode;
		}
		
		/**
		*
		* Return the parent of the specified group
		*
		* Return the objid of the parent of the specified group, if it exists.
		*
		* @access	public
		*
		* @param	string		$ident		Identity of group
		*
		* @return	int			objid of parent group, else NULL
		*
		* TODO: Test parentGroup method
		*/
		function parentGroup( $ident ) {
			
			$returnCode = FALSE;
			
			$dbconn = $this->dbconn;
			$sql = "SELECT parent FROM #__tackle_group WHERE ident = '$ident'";
			$record = &$dbconn->Execute( $sql );
			if ( $record->rowCount() == 1 ) {
				$returnCode = $record->fields[0];
			}
			
			return $returnCode;
		}
	
	////////////////////////////////////////////////////////////////////////
	// Resource Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Create new resource
		*
		* Wrapper to add a new resource to the database
		*
		* @access	public
		*
		* @param	string		$ident		Resource identity
		* @param	string		$descr		Resource description
		* @param	boolean		$enabled	Resource status
		*
		* @return	int			objid of new resource, else FALSE
		*
		*/
		function createResource( $ident, $descr = NULL, $enabled = TRUE ) {
			if ( !$descr ) $descr = $ident;
			return $this->create( 'resource', $ident, $descr, $enabled );
		}
		
		/**
		*
		* Remove a resource
		*
		* Wrapper to remove a resource from the database
		*
		* @access	public
		*
		* @param	string		$ident		Resource identity
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeResource( $ident ) {
			
			$returnCode = FALSE;
			
			// Remove actions and permissions and unlink children before removing group
			$this->removePermission( '*', '*', $ident );
			$returnCode = $this->removeResourceChildren( $ident );
			$returnCode = $returnCode && $this->removeResourceActions( $ident );
			$returnCode = $returnCode && $this->remove( 'resource', $ident );
			
			return $returnCode;
		}
		
		/**
		*
		* Link resource to a parent resource
		*
		* Link a resource to another resource (the parent). The child will
		* eventually inherit characteristics from the parent.
		*		
		* @param	string		$ident				Identity of resource
		* @param	string		$parentIdent		Identity of parent resource
		*
		* access	public
		*
		*/
		function linkResourceToParent( $ident, $parentIdent ) {
			return $this->linkParent( 'resource', $ident, $parentIdent );
		}
		
		/**
		*
		* Unlink resource from parent resource
		*
		* Disenherit the resource from its parent.
		*
		* @param	string		$ident				Identity of resource
		*
		* @access	public
		*
		* @return	int		Objid of resource
		*
		*/
		function unlinkResourceFromParent( $ident ) {
			return $this->unlinkParent( 'resource', $ident );
		}
		
		/**
		*
		* Dereference all children referencing resource
		*
		* Dereference all children that reference this resource.
		*
		* @access	private
		*
		* @param	string		$ident	identity of parent resource
		*
		* @return	boolean		TRUE if removed, else FALSE
		*
		*/
		function removeResourceChildren( $ident ) {
			
			$returnCode = FALSE;
			
			$objid = $this->getObjid( 'resource', $ident );	
			if ( $objid ) {
				$dbconn = $this->dbconn;
				$sql = "UPDATE #__tackle_resource SET parent = NULL WHERE parent = $objid";
				$dbconn->Execute( $sql );
				$returnCode = TRUE;
			}
			return $returnCode;
		}
		
		/**
		*
		* Delete all actions from a resource
		*
		* Delete all actions from a resource.
		*
		* @access	public
		*
		* @param	string	Identity of resource
		*
		*/
		function removeResourceActions( $ident ) {
			
			// FIXME: Remove all permissions related to the actions.
			
			// Get objid of resource
			$resourceObjid = $this->getObjid( 'resource', $ident );
			$db = $this->dbconn;
			
			if ( $resourceObjid ) {
				// Remove permissions related to actions.
				$this->removePermission( '*', '*', $ident );
				$sql = "DELETE FROM #__tackle_action WHERE resourceid = $resourceObjid";
				$db->Execute( $sql );
			}
		}
	
	
	////////////////////////////////////////////////////////////////////////
	// Action Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Add action to a resource
		*
		* Create an action and add it to a resource.
		*
		* @access	public
		*
		* @param	string	Identity of resource
		* @param	string	Identity of action
		*
		* @return	string	Identity of resource
		*
		*/
		function createAction( $resourceIdent, $actionIdent, $descr = NULL, $enabled = TRUE ) {
			
			$returnCode = FALSE;
			
			// Set defaults for args
			if ( !$descr ) $descr = $actionIdent;
			
			$db = $this->dbconn;
			
			$resourceObjid = $this->getObjid( 'resource', $resourceIdent );
			if ( $resourceObjid ) {
				
				// Create the action using getInsertSQL
				
				$sql = "SELECT * from #__tackle_action WHERE ident = '$actionIdent' and resourceId = $resourceObjid";
				$record = $db->Execute( $sql );
				
				if ( $record && $record->RecordCount() == 0 ) {
					// Initialize and build the new record
					$newRecord = new stdClass;
					$newRecord->resourceid = $resourceObjid;
					$newRecord->enabled = $enabled;
					$newRecord->ident = $actionIdent;
					$newRecord->descr = $descr;
					$db->insertObject( '#__tackle_action', $newRecord);
					$returnCode = $actionIdent;
				}
			}	
			
			return $returnCode;
		}
		
		/**
		*
		* Fetch action objid to a resource action
		*
		* Return an action's objid given resource and action identifier.
		*
		* @access	private
		*
		* @param	string	$actionIdent	Identity of action
		* @param	string	$resourceIdent	Identity of resource
		*
		* @return	int		Objid of resource if available, else FALSE
		*
		*/
		function getActionObjid( $actionIdent, $resourceIdent ) {
			
			$returnCode = FALSE;
			
			$db = $this->dbconn;
			$resourceObjid = $this->getObjid( 'resource', $resourceIdent );
			if ( $resourceObjid ) {
				$sql = "SELECT objid FROM #__tackle_action WHERE ident = '$actionIdent' AND resourceid = $resourceObjid";
				$record = $db->Execute( $sql );
				if ( $record ) {
					$returnCode = $record->data[0][0];
				}
			}
			return $returnCode;
		}
		
		/**
		*
		* Delete an action from a resource
		*
		* Delete an action from a resource.
		*
		* @access	public
		*
		* @param	string	Identity of resource
		* @param	string	Identity of action
		*
		* @return	bool	TRUE if action no longer exists, else FALSE
		*
		*/
		function removeAction( $actionIdent, $resourceIdent ) {
			
			$returnCode = FALSE;
			// Remove permissions related to the action
			$this->removePermission( '*', '*', $resourceIdent, $actionIdent );
			$returnCode = $this->removeByObjid( 'action', $this->getActionObjid( $actionIdent, $resourceIdent ) );
			
			return $returnCode;
		}
		
		
	////////////////////////////////////////////////////////////////////////
	// Permission Wrapper Methods
	////////////////////////////////////////////////////////////////////////
	
		/**
		*
		* Set or change a permission
		*
		* Create a new permission or change an existing one.
		*
		* @access	public
		*
		* @param	string		$reqType	Type of requestor (member or group)
		* @param	string		$reqIdent	Identity of requestor
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		* @param	string		$permit		"ALLOW" or "DENY" (future "UNSET")
		* @param	string		$state		"ENABLED" or "DISABLED"
		*
		*/
		function addPermission( $reqType, $reqIdent, $resource, $action, $permit = "ALLOW", $status = "ENABLED" ) {
			// TODO: Allow permission for resources (without actions)
			
			// Set the status
			if ( strtoupper( $status ) == "ENABLED" ) {
				$status = 1;
			} else {
				$status = 0;
			}
			
			// Set the permission
			switch ( strtoupper( $permit ) ) {
				case "ALLOW":
					$permit = 1;
					break;
				case "DENY":
					$permit = 0;
					break;
				default:		// Default to DENY
					$permit = 0;
					break;
			}
			
			// Set the requestor info
			switch ( strtoupper( $reqType ) ) {
				case "MEMBER":
					$memberObjid = $this->getObjid( 'member', $reqIdent );
					$groupObjid = 0;
					break;
				case "GROUP":
					$groupObjid = $this->getObjid( 'group', $reqIdent );
					$memberObjid = 0;
					break;
				default:
					die ( "Not a valid requestor type" );
					break;
			}
			
			// Set the resource and action objids
			$resourceObjid = $this->getObjid( 'resource', $resource );
			

			$actionObjid = $this->getActionObjid( $action, $resource );
			
			// FIXME: This is failing. Not creating any permissions
			//print "<H1>TEST: $resourceObjid, $actionObjid, $groupObjid, $memberObjid</H1>";
			//if ( $resourceObjid && $actionObjid && $groupObjid != NULL && $memberObjid != NULL ) {
			if ( $resourceObjid && $actionObjid ) {
				
				$db = $this->dbconn;
				// Use getinsertupdatesql to handle the sql
				// Select the row. If it's empty we have an insert, otherwise we need to do an update.
				$sql = "SELECT * FROM #__tackle_permission WHERE resourceid = $resourceObjid "
					."AND actionid = $actionObjid AND memberid = $memberObjid AND groupid = $groupObjid";
				
				 $db->setQuery( $sql );
				$oRow = $db->loadObject();
				
				if (  isset( $oRow->resourceId ) ) {
					// UPDATE
					//echo "update, ".$record->fields['resourceid']."<BR>";
/*
					$updRecord = new stdClass();
					$updRecord->resourceId = $resourceObjid;
					$updRecord->actionId = $actionObjid;
					$updRecord->groupId = $groupObjid;
					$updRecord->memberId = $memberObjid;
					$updRecord->access = $permit;
					$updRecord->enabled = $status;
					
					$updateSql = $db->updateObject( '#__tackle_permission', $updRecord );
*/
			$updateSql = "UPDATE #__tackle_permission SET `access`='$permit',`enabled`='$status' WHERE  resourceid = $resourceObjid "
					."AND actionid = $actionObjid AND memberid = $memberObjid AND groupid = $groupObjid";

					if ( $updateSql ) $db->Execute( $updateSql );
				} else {
					// INSERT
					//echo "add, ".$record->fields['resourceid']."<BR>";
					$newRecord = new stdClass;
					$newRecord->resourceid = $resourceObjid;
					$newRecord->actionid = $actionObjid;
					$newRecord->groupid = $groupObjid;
					$newRecord->memberid = $memberObjid;
					$newRecord->access = $permit;
					$newRecord->enabled = $status;
					
					$insertSql = $db->insertObject( '#__tackle_permission', $newRecord );
					
				}
			}
		}
		
		/**
		* Change a permission
		*
		* Change an existing permission.
		*
		* @access	public
		*
		* @param	string		$reqType	Type of requestor (member or group)
		* @param	string		$reqIdent	Identity of requestor
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		* @param	string		$permit		"ALLOW" or "DENY" (future "UNSET")
		* @param	string		$state		"ENABLED" or "DISABLED"
		*/
		function changePermission( $reqType, $reqIdent, $resource, $action, $permit = "UNSET", $status = "DISABLED" ) {
			return $this->addPermission( $reqType, $reqIdent, $resource, $action, $permit, $status );
		}
		
		/**
		*
		* Remove a permission
		*
		* Removes am existing permission. You can pass reqIdent, resource, or
		* action the string '*' to ignore that object during the delete. I.e.,
		* specifying * for everything execpt resource, will delete all permissions
		* with the specified resource. Be VERRRRRY careful here!!!! Specifying
		* a * for resource, and either reqIdent or reqType is enough to remove
		* ALL permissions from the table!
		*
		* @access	public
		*
		* @param	string		$reqType	Type of requestor (member or group)
		* @param	string		$reqIdent	Identity of requestor
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		*
		*/
		function removePermission( $reqType = '*', $reqIdent = '*', $resource = '*', $action = '*') {
			
			$returnCode = FALSE;
			$fail = FALSE;
			
			// TODO: Make action optional: if action isn't specified, remove requestor
			// from entire resource.
			
			$sqlWhere = '';
			
			if ( $action != '*' ) {
				// Build WHERE clause for action.
				// This will properly fail if there's no valid resource for the action.
				$actionObjid = $this->getActionObjid( $action, $resource );
				if ( $actionObjid ) {
					$sqlWhere ? $sqlWhere .= " AND actionid = $actionObjid" : $sqlWhere .= " WHERE actionid = $actionObjid";
				}
			}
			
			if ( $resource != '*' ) {
				// Build WHERE clause for resource
				$resourceObjid = $this->getObjid( 'resource', $resource );
				if ( $resourceObjid ) {
					$sqlWhere ? $sqlWhere .= " AND resourceid = $resourceObjid" : $sqlWhere .= " WHERE resourceid = $resourceObjid";
				}
			}
			
			if ( $reqIdent != '*' && $reqType != '*' ) {
				// Build WHERE clause for requestor
				switch ( strtoupper( $reqType ) ) {
					case "MEMBER":
						$memberObjid = $this->getObjid( 'member', $reqIdent );
						$groupObjid = 0;
						break;
					case "GROUP":
						$groupObjid = $this->getObjid( 'group', $reqIdent );
						$memberObjid = 0;
						break;
				}
				
				if ( $memberObjid + $groupObjid > 0 ) {
					$sqlWhere ? $sqlWhere .= " AND memberid = $memberObjid" : $sqlWhere .= " WHERE memberid = $memberObjid";
					$sqlWhere .= " and groupid = $groupObjid";
				}
			}
			
			$db = $this->dbconn;
			$sql = "DELETE FROM #__stackle_permission $sqlWhere";		
			$db->Execute( $sql );
		}
		
		/**
		* Check requestor for requested permissions
		*
		* Check a member or group for access to the specified resource and action.
		*
		* @access	public
		*
		* @param	string		$reqType	Type of requestor (member or group)
		* @param	string		$ident		Identity of requestor
		* @param	string		$action		Identity of action to check
		* @param	string		$resource	Identity of action's resource
		* @return	string		ALLOW/DENY if ACL exists, else NULL
		*/
		function authorizedRequestor( $reqType, $ident, $action, $resource ){
			
			$returnCode = FALSE;
			
			$reqObjid = $this->getObjid( $reqType, $ident );
			$actionObjid = $this->getObjid( 'action', $action );
			$resourceObjid = $this->getObjid( 'resource', $resource );
			
			if ( $reqObjid && $actionObjid && $resourceObjid ) {
				$returnCode = $this->authorizedRequestorByObjid( $reqType, $reqObjid, $actionObjid, $resourceObjid );
			}
			return $returnCode;
		}
		
		/**
		* Check requestor for requested permissions
		*
		* Check a member or group for access to the specified resource and action.
		*
		* @access	private
		*
		* @param	string		$reqType        Type of requestor (member or group)
		* @param	string		$reqObjid		Objid of requestor
		* @param	string		$actionObjid	Objid of action to check
		* @param	string		$resourceObjid	Objid of action's resource
		* @return	string		ALLOW/DENY if ACL exists, else NULL
		*/
		function authorizedRequestorByObjid( $reqType, $reqObjid, $actionObjid, $resourceObjid ){
			
			$returnCode = FALSE;
			
			// Query the DB to verify that ident has resource->action permissions.
			$db = $this->dbconn;
			
			$sql = "SELECT p.access "
				."FROM #__tackle_permission p, #__tackle_".$reqType." q, #__tackle_resource r, #__tackle_action a "
				."WHERE p.resourceid = r.objid "
				."AND p.actionid = a.objid "
				."AND p.".$reqType."id = q.objid "
				."AND q.objid = $reqObjid "
				."AND r.objid = $resourceObjid AND a.objid = $actionObjid "
				."AND a.enabled = 1 "
				."AND r.enabled = 1 "
				."AND q.enabled = 1 "
				."AND p.enabled = 1";
				
			$record = &$db->Execute( $sql );
			// Requestor is only permitted if (1) we get a row back and
			// (2) the value is '1'. 
			if ( $record->RecordCount() == 1 ) {
				if ( $record->data[0][0] == 1 ) {
					$returnCode = 'ALLOW';
				} elseif ( $record->data[0][0] == 0 ) {
					$returnCode = 'DENY';
				}
			} else {
				// Handle recursive authorizations.
				// Patch submitted by Tom Johnson (Thanks Tom!)
				$resourceObjid = $this->getParentByObjid( 'resource', $resourceObjid );
				if ( $resourceObjid ) {
					$auth = $this->authorizedRequestorByObjid( $reqType, $reqObjid, $actionObjid, $resourceObjid);
					}
				if( isset( $auth ) ) $returnCode = $auth;
			}
			return $returnCode;
		}
}


// User-space functions

function tackle_authorizedMember( $member, $action, $resource ) {
	$check = new tackle();
	return $check->authorizedMember( $member, $action, $resource );
}

function tackle_authorizedGroup( $group, $action, $resource ) {
	$check = new tackle();
	return $check->authorizedGroup( $group, $action, $resource );
}

?>
